﻿using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using JasLib.Extensions;
using JasLib.Interop;
using JasLib.Interop.Win32;
using JasLib.Diagnostics;
using System.Reflection;

namespace JasLib.Threading
{
    public static class AsyncThreadSwitcher
    {
        /***********************************************************************************************/
        /// <summary>
        /// Facilitates fast switching to the thread pool without preserving Task or other context.
        /// Best suited for "async void" methods.
        /// </summary>
        private class UnsafeSwitchToThreadPoolAwaitable : UnsafeAwaitable
        {
            private bool _bRepostAnywayIfAlreadyInThreadPool = false;

            public UnsafeSwitchToThreadPoolAwaitable(bool bRepostAnywayIfInThreadPool)
            {
                _bRepostAnywayIfAlreadyInThreadPool = bRepostAnywayIfInThreadPool;
                return;
            }

            public override bool IsCompleted
            {
                get { return (!_bRepostAnywayIfAlreadyInThreadPool && Thread.CurrentThread.IsThreadPoolThread); }
            }

            public override void OnCompleted(Action continuation)
            {
                ThreadPool.UnsafeQueueUserWorkItem(c => ((Action)c)(), continuation);
                return;
            }
        }

        /*******************************************************************************************/
        public static UnsafeAwaitable UnsafeSwitchToThreadPoolAsync(bool bRepostAnywayIfAlreadyInThreadPool = false)
        {
            return new UnsafeSwitchToThreadPoolAwaitable(bRepostAnywayIfAlreadyInThreadPool);
        }

        /***********************************************************************************************/
        /// <summary>
        /// Facilitates fast switching to a brand new thread without preserving Task or other context.
        /// Best suited for "async void" methods.
        /// </summary>
        private class SwitchToNewManagedThreadAwaitable : Awaitable<Thread>
        {
            private ApartmentState _apartmentState;
            private bool _bIsBackground = true;
            private int _iMaxStackSize;

            private Thread _thread = null;
            private Action _continuation = null;
            private Action _postContinuationAction = null;

            public SwitchToNewManagedThreadAwaitable(
                ApartmentState apartmentState,
                bool bIsBackground,
                int iMaxStackSize,
                Action postContinuationAction)
            {
                _apartmentState = apartmentState;
                _bIsBackground = bIsBackground;
                _iMaxStackSize = iMaxStackSize;
                _postContinuationAction = postContinuationAction;
                return;
            }

            public override bool IsCompleted
            {
                get { return false; }
            }

            public override void OnCompleted(Action continuation)
            {
                _continuation = continuation;
                _thread = new Thread(ThreadEntryPoint, _iMaxStackSize);
                _thread.IsBackground = _bIsBackground;
                _thread.SetApartmentState(_apartmentState);
                _thread.Start(this);
                return;
            }

            private static void ThreadEntryPoint(object objParam)
            {
                try
                {
                    var awaitable = (objParam as SwitchToNewManagedThreadAwaitable);
                    awaitable._continuation();
                    if (awaitable._postContinuationAction != null)
                        awaitable._postContinuationAction();
                }
                finally //catch (Exception ex)
                {
                }
                return;
            }

            public override Thread GetResult()
            {
                /// Clean out all the references;
                /// we don't need them anymore, and Awaitables are potentially long-lived.
                /// Only thing is, don't kill _postContinuationAction because it does get used.
                _continuation = null;

                return GeneralUtilities.Swap(ref _thread, null);
            }
        }

        /*******************************************************************************************/
        /// <summary>
        /// 
        /// </summary>
        /// <param name="apartmentState"></param>
        /// <param name="bIsBackground">
        /// It is very important to be aware of this value because if you switch to a background thread
        /// while exiting the only foreground thread, your process will terminate.
        /// </param>
        /// <param name="iMaxStackSize"></param>
        /// <param name="postContinuationAction">
        /// This allows you to establish a delegate queuing loop so that you can return to
        /// the thread if you should leave it for any routine reason (such as await-ing to another thread).
        /// </param>
        public static Awaitable<Thread> SwitchToNewManagedThreadAsync(
            ApartmentState apartmentState = ApartmentState.Unknown,
            bool bIsBackground = true,
            int iMaxStackSize = 100000,
            Action postContinuationAction = null)
        {
            return new SwitchToNewManagedThreadAwaitable(apartmentState, bIsBackground, iMaxStackSize, postContinuationAction);
        }

        /***********************************************************************************************/
        /// <summary>
        /// Facilitates fast switching to the thread pool without preserving Task or other context.
        /// </summary>
        private class SwitchToNewDispatcherAwaitable : Awaitable<Dispatcher>
        {
            private bool _bIsBackground = false;
            private bool _bExitAfterContinuation = false;

            private Action _continuation = null;
            private Thread _thread = null;
            private Dispatcher _dispatcher = null;
            private DispatcherOperation _dispatcherOperation = null;

            public SwitchToNewDispatcherAwaitable(bool bIsBackground, bool bExitAfterContinuation)
            {
                _bIsBackground = bIsBackground;
                _bExitAfterContinuation = bExitAfterContinuation;
                return;
            }

            public override bool IsCompleted
            {
                get { return false; }
            }

            public override void OnCompleted(Action continuation)
            {
                _continuation = continuation;
                _thread = new Thread(ThreadEntryPoint);
                _thread.IsBackground = _bIsBackground;
                _thread.SetApartmentState(ApartmentState.STA);
                _thread.Start(this);
                return;
            }

            /// <summary>
            /// Static function means that the Awaitable instance can be GC'd after GetResult().
            /// </summary>
            private static void ThreadEntryPoint(object objParam)
            {
                try
                {
                    var awaitable = (objParam as SwitchToNewDispatcherAwaitable);
                    awaitable._dispatcher = Dispatcher.CurrentDispatcher;
                    awaitable._dispatcherOperation = awaitable._dispatcher.BeginInvoke(new Action<SwitchToNewDispatcherAwaitable>(DispatcherEntryPoint), DispatcherPriority.Send, awaitable);
                    Dispatcher.Run();
                }
                finally //catch (Exception ex)
                {
                }
                return;
            }

            private static void DispatcherEntryPoint(SwitchToNewDispatcherAwaitable awaitable)
            {
                awaitable._continuation();
                if (awaitable._bExitAfterContinuation)
                    awaitable._dispatcher.BeginInvokeShutdown(DispatcherPriority.Normal);
                return;
            }

            public override Dispatcher GetResult()
            {
                return _dispatcher;
            }
        }

        /*******************************************************************************************/
        public static Awaitable<Dispatcher> SwitchToNewDispatcherAsync(
            bool bIsBackground = false,
            bool bExitAfterContinuation = false)
        {
            return new SwitchToNewDispatcherAwaitable(bIsBackground, bExitAfterContinuation);
        }

        /***********************************************************************************************/
        private class SwitchToDispatcherAwaitable : Awaitable
        {
            private static FieldInfo s_dispatcherFrameDepthField = typeof(Dispatcher).GetAllFields().FirstOrDefault(f => f.Name == "_frameDepth");

            static SwitchToDispatcherAwaitable()
            {
                DebugUtilities.Assert(s_dispatcherFrameDepthField != null);
                return;
            }

            private bool _bAlreadyInDispatcherAndLoopNotYetRunning = false;
            private Dispatcher _dispatcher;
            private DispatcherPriority _priority;

            public SwitchToDispatcherAwaitable(Dispatcher dispatcher, DispatcherPriority priority)
            {
                if (dispatcher == null)
                    throw new ArgumentNullException("dispatcher");
                _dispatcher = dispatcher;
                _priority = priority;
                return;
            }

            public override bool IsCompleted
            {
                get
                {
                    if (_dispatcher == Dispatcher.CurrentDispatcher)
                    {
                        /// TODO: The WPF team needs to create an IsRunning property or something.
                        int iFrameDepth = (int)s_dispatcherFrameDepthField.GetValue(_dispatcher);

                        _bAlreadyInDispatcherAndLoopNotYetRunning = (iFrameDepth == 0);
                        return !_bAlreadyInDispatcherAndLoopNotYetRunning;
                    }

                    return false;
                }
            }

            public override void OnCompleted(Action continuation)
            {
                _dispatcher.BeginInvoke(continuation, _priority);

                if (_bAlreadyInDispatcherAndLoopNotYetRunning)
                    Dispatcher.Run();

                return;
            }

            public override void GetResult()
            {
                _dispatcher = null;
                return;
            }
        }

        /***********************************************************************************************/
        public static Awaitable SwitchToDispatcherAsync(Dispatcher dispatcher, DispatcherPriority priority)
        {
            return new SwitchToDispatcherAwaitable(dispatcher, priority);
        }

        /***********************************************************************************************/
        private class SwitchToSynchronizeInvokeAwaitable : Awaitable
        {
            private ISynchronizeInvoke _invoker;
            private IAsyncResult _result = null;

            public SwitchToSynchronizeInvokeAwaitable(ISynchronizeInvoke invoker)
            {
                if (invoker == null)
                    throw new ArgumentNullException("invoker");
                _invoker = invoker;
                return;
            }

            public override bool IsCompleted
            {
                get { return !_invoker.InvokeRequired; }
            }

            public override void OnCompleted(Action continuation)
            {
                _result = _invoker.BeginInvoke(continuation, Enumerable.Empty<object>().AsArray());
                return;
            }

            public override void GetResult()
            {
                _invoker.EndInvoke(_result);
                _invoker = null;
                _result = null;
                return;
            }
        }

        /***********************************************************************************************/
        /// <summary>
        /// This is used by implementers of <see cref="ISynchronizeInvoke"/>, namely WinForms controls.
        /// </summary>
        public static Awaitable SwitchToSynchronizeInvokeAsync(ISynchronizeInvoke invoker)
        {
            return new SwitchToSynchronizeInvokeAwaitable(invoker);
        }

        /***********************************************************************************************/
        private class SwitchToSynchronizationContextAwaitable : Awaitable
        {
            private SynchronizationContext _context;

            public SwitchToSynchronizationContextAwaitable(SynchronizationContext context)
            {
                _context = context;
                return;
            }

            public override bool IsCompleted
            {
                get
                {
                    return (_context == SynchronizationContext.Current ||
                        /// There is only one ThreadPool, so we can do this custom check.
                        (Thread.CurrentThread.IsThreadPoolThread &&
                        (_context == null || _context.GetType() == typeof(SynchronizationContext))));
                }
            }

            public override void OnCompleted(Action continuation)
            {
                if (_context != null)
                    _context.Post(c => ((Action)c)(), continuation);
                else
                    ThreadPool.QueueUserWorkItem(c => ((Action)c)(), continuation);
                return;
            }

            public override void GetResult()
            {
                _context = null;
                return;
            }
        }

        /***********************************************************************************************/
        public static Awaitable SwitchToSynchronizationContextAsync(SynchronizationContext context)
        {
            return new SwitchToSynchronizationContextAwaitable(context);
        }

        /***********************************************************************************************/
        /// <summary>
        /// Facilitates fast switching to a brand new thread without preserving Task or other context.
        /// Best suited for "async void" methods.
        /// </summary>
        private class UnsafeSwitchToNewUnmanagedThreadAwaitable : UnsafeAwaitable
        {
            private int _iMaxStackSize;

            private Action _continuation = null;
            private Action _postContinuationAction = null;

            public UnsafeSwitchToNewUnmanagedThreadAwaitable(
                int iMaxStackSize,
                Action postContinuationAction)
            {
                _iMaxStackSize = iMaxStackSize;
                _postContinuationAction = postContinuationAction;
                return;
            }

            public override bool IsCompleted
            {
                get { return false; }
            }

            public override void OnCompleted(Action continuation)
            {
                _continuation = continuation;

                SECURITY_ATTRIBUTES securityAttributes = new SECURITY_ATTRIBUTES();

                uint uiThreadId;
                using (var hThread = KERNEL32.CreateThread(
                    ref securityAttributes,
                    _iMaxStackSize,
                    THREAD_START_ROUTINE,
                    0,
                    0,
                    out uiThreadId));

                return;
            }

            private uint THREAD_START_ROUTINE(UINT_PTR lpParameter)
            {
                try
                {
                    _continuation();
                    if (_postContinuationAction != null)
                    {
                        _postContinuationAction();
                        _postContinuationAction = null;
                    }
                }
                finally //catch (Exception ex)
                {
                }
                return 0;
            }

            public override void GetResult()
            {
                /// Clean out all the references;
                /// we don't need them anymore, and Awaitables are potentially long-lived.
                /// Only thing is, don't kill _postContinuationAction because it does get used.
                _continuation = null;
                return;
            }
        }

        /*******************************************************************************************/
        /// <summary>
        /// 
        /// </summary>
        /// <param name="apartmentState"></param>
        /// <param name="bIsBackground">
        /// It is very important to be aware of this value because if you switch to a background thread
        /// while exiting the only foreground thread, your process will terminate.
        /// </param>
        /// <param name="iMaxStackSize"></param>
        /// <param name="postContinuationAction">
        /// This allows you to establish a delegate queuing loop so that you can return to
        /// the thread if you should leave it for any routine reason (such as await-ing to another thread).
        /// </param>
        public static UnsafeAwaitable UnsafeSwitchToNewUnmanagedThreadAsync(
            int iMaxStackSize = 100000,
            Action postContinuationAction = null)
        {
            return new UnsafeSwitchToNewUnmanagedThreadAwaitable(iMaxStackSize, postContinuationAction);
        }

        /***********************************************************************************************/
        private class SwitchToFinalizerThreadAwaitable : UnsafeAwaitable<Thread>
        {
            private static Thread s_finalizerThread = null;

            private bool _bForceImmediateGarbageCollection = false;

            private class FinalizerProbe
            {
                private Action _continuation = null;
                public FinalizerProbe(Action continuation)
                {
                    _continuation = continuation;
                    return;
                }
                ~FinalizerProbe()
                {
                    s_finalizerThread = Thread.CurrentThread;
                    _continuation();
                    return;
                }
            }

            public SwitchToFinalizerThreadAwaitable(bool bForceImmediateGarbageCollection)
            {
                _bForceImmediateGarbageCollection = bForceImmediateGarbageCollection;
                return;
            }

            public override bool IsCompleted
            {
                get
                {
                    return (Thread.CurrentThread == s_finalizerThread);
                }
            }

            public override void OnCompleted(Action continuation)
            {
                /// Do NOT keep a reference to this dummy instance (no member initializer lists either).
                /// It needs to get GC'd to run our entry point,
                /// and any superfluous reference/access will keep it alive for the whole method in DEBUG builds.
                new FinalizerProbe(continuation);

                if (_bForceImmediateGarbageCollection)
                {
                    GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
                    GC.WaitForPendingFinalizers();
                }
                return;
            }

            public override Thread GetResult()
            {
                return s_finalizerThread;
            }
        }

        /***********************************************************************************************/
        /// <summary>
        /// A truly morbid method; this is like visiting the morgue!
        /// </summary>
        /// <remarks>
        /// There is only one practical use for this, besides the novelty,
        /// and that is to identify and assign Thread.Name on the finalizer thread.
        /// A fair warning, adventurer: do not dwell too long in Hades or you will constipate the GC;
        /// and if you do so during shutdown, the CLR will time out and terminate the process.
        /// </remarks>
        public static UnsafeAwaitable<Thread> SwitchToFinalizerThreadAsync(bool bForceImmediateGarbageCollection = true)
        {
            return new SwitchToFinalizerThreadAwaitable(bForceImmediateGarbageCollection);
        }
    }
}
